package com.mengxuegu.security.config;

import com.mengxuegu.security.authorize.AuthorizeConfigurerManager;
import com.mengxuegu.security.authrntication.code.ImageCodeValidateFilter;
import com.mengxuegu.security.authrntication.session.CustomLogoutHandler;
import com.mengxuegu.security.mobile.MobileAuthenticationConfig;
import com.mengxuegu.security.mobile.MobileValidateFilter;
import com.mengxuegu.security.properites.AuthenticationProperties;
import com.mengxuegu.security.properites.SecurityProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.session.InvalidSessionStrategy;
import org.springframework.security.web.session.SessionInformationExpiredStrategy;

import javax.sql.DataSource;

/**
 * @author william
 * @create 2021-07-01 21:57
 **/
@Configuration
@EnableWebSecurity//开启speingsecurity过滤链
@EnableGlobalMethodSecurity(prePostEnabled = true)//开启注解方法级别的控制
public class SpringSercurityConfig extends WebSecurityConfigurerAdapter {
    Logger logger = LoggerFactory.getLogger(SpringSercurityConfig.class);

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Autowired
    private UserDetailsService customUserDetailsService;

    /**
     * 认证管理器
     * 1.认证信息(用户名,密码)
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//        //数据库存储的密码必须是加密后的
//        String password = passwordEncoder().encode("12345");
//        logger.info("加密后存储的密码:" + password);
//        auth.inMemoryAuthentication().withUser("mengxuegu")
//                .password(password).authorities("ADMIN");
        auth.userDetailsService(customUserDetailsService);
    }

    @Autowired
    private DataSource dataSource;

    @Bean
    public JdbcTokenRepositoryImpl jdbcTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        //是否启动项目时自动创建表 true自动创建
        //jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    }

    @Autowired
    private SecurityProperties securityProperties;

    @Autowired
    private AuthenticationSuccessHandler customAuthenticationSuccessHandler;
    @Autowired
    private AuthenticationFailureHandler customAuthenticationFailureHandler;

    @Autowired
    private ImageCodeValidateFilter imageCodeValidateFilter;
    @Autowired
    private MobileValidateFilter mobileValidateFilter;
    @Autowired
    private MobileAuthenticationConfig mobileAuthenticationConfig;
    @Autowired
    private InvalidSessionStrategy invalidSessionStrategy;
    @Autowired
    private CustomLogoutHandler customLogoutHandler;
    /**
     * 当同个用户session数量超过指定值后,会调用这个实现类
     */
    @Autowired
    private SessionInformationExpiredStrategy sessionInformationExpiredStrategy;

    @Autowired
    private AuthorizeConfigurerManager authorizeConfigurerManager;
    /**
     * 资源权限配置
     * 1.被拦截的资源
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        AuthenticationProperties authentication = securityProperties.getAuthentication();
//        http.httpBasic() //采用httpbasic认证方式
        http.addFilterBefore(mobileValidateFilter, UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(imageCodeValidateFilter, UsernamePasswordAuthenticationFilter.class)
                .formLogin()  //采用表单认证方式
                .loginPage(authentication.getLoginPage())// 交给/login/page 响应认证(登录)页面
                .loginProcessingUrl(authentication.getLoginProcessingUrl())//登录表单提交处理Url,默认是/login
                .usernameParameter(authentication.getUsernameParameter())//默认用户名的属性名为username
                .passwordParameter(authentication.getPasswordParameter())//默认密码的属性名为password
                .successHandler(customAuthenticationSuccessHandler)
                .failureHandler(customAuthenticationFailureHandler)
//                .and()
//                .authorizeRequests()//认证请求
//                .antMatchers(authentication.getLoginPage(),
//                        authentication.getImageCodeUrl(),
//                        authentication.getMobileCodeUrl(),
//                        authentication.getMobilePageUrl()
//                ).permitAll()//不需要认证所有人都可以访问这个请求  放行这个请求/login/page
//                .anyRequest().authenticated()//所有访问该应用的http请求都要通过身份认证才能访问
                .and().rememberMe()
                .tokenRepository(jdbcTokenRepository())//保存登录信息
                .tokenValiditySeconds(authentication.getTokenValiditySeconds())  //一星期后token失效
                .and().sessionManagement()
                .invalidSessionStrategy(invalidSessionStrategy)
                .maximumSessions(1)//每个用户在系统中最多可以有多少个session
                .expiredSessionStrategy(sessionInformationExpiredStrategy)//超过最大数执行这个实现类
        .maxSessionsPreventsLogin(true)//当用户达到最大的session数,则不允许后面的再登录
        .sessionRegistry(sessionRegistry())
        .and().and()
        .logout().addLogoutHandler(customLogoutHandler)//退出清楚缓存
        .logoutUrl("/user/logout")//自定义退出Url
        .logoutSuccessUrl("/mobile/page")//退出成功后进入手机登录页面
        .deleteCookies("JSESSIONID")//退出后删除什么cookies值
        ;
        http.csrf().disable();//关闭跨站请求伪造 可以使用get方式请求 /logout 否则只接受post请求
        http.apply(mobileAuthenticationConfig);

        // 将所有的授权配置统一的起来
        authorizeConfigurerManager.configure(http.authorizeRequests());

    }

    /**
     * 为解决退出重新登录的问题
     * @return
     */
    @Bean
    public SessionRegistry sessionRegistry(){
        return new SessionRegistryImpl();
    }

    /**
     * 一般是针对静态资源放行
     *
     * @param web
     */
    @Override
    public void configure(WebSecurity web) {
        web.ignoring().antMatchers(securityProperties.getAuthentication().getStaticPaths());
    }
}
